function Compile(el, vm) {
    // 保存vm，用于后面去data中的数据
    this.$vm = vm
    // 找到并且保存el
    this.$el = this.isElementNode(el) ? el : document.querySelector(el)
    if (this.$el) {
        // 此处将节点暂存，封装在fragment中，这样不用更新整个页面，只需更新fragment
        this.$fragment = this.node2fragment(this.$el)
        // 这一步最关键，编译fragment中所有层次子节点
        this.init()
        // 将处理好的fragment放回元素中
        this.$el.appendChild(this.$fragment)
    }
}

Compile.prototype = {
    node2fragment: function (el) {
        let fragment = document.createDocumentFragment()
        let child
        while (child = el.firstChild) {
            fragment.appendChild(child)
        }
        return fragment
    },
    init: function () {
        this.compileElement(this.$fragment)
    },
    compileElement: function (el) {
        //空行是文本节点，需要忽略，只用符合大括号表达式的
        let childNodes = el.childNodes
        Array.from(childNodes).forEach(node => {
            let text = node.textContent
            // 正则匹配大括号表达式
            let reg = /\{\{(.*)\}\}/;
            if (this.isElementNode(node)) {
                //因为是元素节点就需要对元素节点上的指令做出处理
                this.compileElementNode(node)
            } else if (this.isTextNode(node) && reg.test(text)) {
                // 因为是文本节点所以调用更新文本节点的方法
                this.compileText(node, RegExp.$1)
            }
            
            // 循环遍历节点，只要有子节点就将它找出来，然后遍历
            if (node.childNodes && node.childNodes.length > 0) {
                this.compileElement(node)
            }

        })
    },

    compileElementNode: function (node) {
        let nodeAttrs = Array.from(node.attributes)
        nodeAttrs.forEach(attr => {
            let name = attr.name
            //需要先判断是不是对应v-开头的指令
            if (this.isDirective(name)) {
                // 如果是事件指令则继续特殊处理
                let dir = name.substring(2)
                let exp = attr.value
                if (this.isEventDirective(dir)) {
                    compileUtil.eventHandler(node, exp, dir, this.$vm)
                }else {
                    //解析普通指令v-html v-text等
                    compileUtil[dir] && compileUtil[dir](node, this.$vm, exp)
                }
                //绑定事件后移除v-指令
                node.removeAttribute(name)
            }
        })
    },

    isDirective: function (attr) {
        return attr.indexOf('v-') === 0
    },

    isEventDirective: function (attr) {
        return attr.indexOf('on') === 0
    },

    compileText: function (node, exp) {
        compileUtil.text(node, this.$vm, exp)
    },

    isElementNode: function (node) {
        return node.nodeType === 1
    },
    isTextNode: function (node) {
        return node.nodeType === 3
    },
}

let compileUtil = {
    eventHandler: function (node, methodName, dir, vm) {
        let eventName = dir.split(':')[1]
        //一定更要methods中有对应方法才能调用
        let fn = vm.$options.methods && vm.$options.methods[methodName]
        if (eventName && fn) {
            //取出对应事件名称，后进行监听，记得改变this指向
            node.addEventListener(eventName, fn.bind(vm), false)
        }
    },
    // 更新text节点
    text: function (node, vm, exp) {
        this.bind(node, vm, exp, 'text')
    },
    // 更新html节点
    html: function (node, vm, exp) {
        this.bind(node, vm, exp, 'html')
    },
    // 更新class节点
    class: function (node, vm, exp) {
        this.bind(node, vm, exp, 'class')
    },
    bind: function (node, vm, exp, dir) {
        //调用不同的更新方法
        let updaterFn = updater[dir + 'Updater']
        updaterFn && updaterFn(node, this._getVMVal(vm, exp))

        new Watcher(vm, exp, function(value) {
            updaterFn && updaterFn(node, value)
        })
    },

    //从vm中找到exp对应的数据
    _getVMVal(vm, exp) {
        let val = vm._data
        //一层一层的剥出最后对应的值
        let expList = exp.split('.')
        expList.forEach(item => {
            val = val[item]
        })
        return val
    }

}

let updater = {
    // 更新节点的textContent
    textUpdater: function (node, value) {
        node.textContent = typeof value == 'undefined' ? '' : value;
    },

    // 更新节点的innerHtml
    htmlUpdater: function (node, value) {
        node.innerHTML = typeof value == 'undefined' ? '' : value;
    },

    // 更新节点的class
    classUpdater: function (node, value) {
        node.className = typeof value == 'undefined' ? '' : value;
    },

}